﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CML.Services.Interfaces;
using CML.Grammer;
using System.Xml;
using CML.Data;
using System.Xml.XPath;
using Newtonsoft.Json;
using CML.Data.Files;

namespace CML.Services.Implementations
{
	public class CMLInterpreterService : ICMLInterpreterService 
	{
		private readonly IXmlService XmlService;
		private readonly IInstructionService InstructionService;
		private readonly IFileService FileService;
		private readonly IPropertyService PropertyService;

		public CMLInterpreterService(IInstructionService instructionService, 
			IXmlService xmlService, 
			IFileService fileService, 
			IPropertyService propertyService)
		{
			InstructionService = instructionService;
			XmlService = xmlService;
			FileService = fileService;
			PropertyService = propertyService;
		}

		public FileResult[] RunScript(RunScriptRequest request)
		{
			if (string.IsNullOrEmpty(request.sourceFolder))
				throw new Exception("No Source folder provided.");
			if(!System.IO.Directory.Exists(request.sourceFolder))
				throw new Exception("Source folder does not exist.");

			if (string.IsNullOrEmpty(request.targetFolder))
				request.writeTarget = false;
			if (request.writeTarget)
			{
				FileService.CreateOrClearFolder(request.targetFolder);
				FileService.CopyDirectory(request.sourceFolder, request.targetFolder, true);
			}

			var p = new Parser();
			var l = p.Parse(request.cml);

			var result = new Dictionary<string, FileResult>();
			foreach (var er in l)
			{
				foreach (var e in er.environments)
				{
					var environmentPath = GetEnvironmentPath(request.sourceFolder, e);
					var targetEnvironmentPath = GetEnvironmentPath(request.targetFolder, e);
					var environmentPathExists = System.IO.Directory.Exists(environmentPath);
					foreach (var f in er.files)
					{
						var relativePath = GetFilePath(e, f.name);
						var fullPath = GetFilePath(environmentPath, f.name);
						var targetFullPath = GetFilePath(targetEnvironmentPath, f.name);
						var key = relativePath;

						FileResult r = null;
						if (result.ContainsKey(key))
						{
							r = result[key];
						}
						else
						{
							r = new FileResult()
							{
								Path = fullPath
							};
							result[key] = r;
						}

						if (!environmentPathExists)
						{
							r.SetErrorCode(ErrorCode.EnvironmentNotFound, e);
							continue;
						}

						if (f is EditXmlFile)
						{
							var ef = (EditXmlFile)f;
							r.Action = "edit";
														
							if (!System.IO.File.Exists(fullPath))
							{
								r.SetErrorCode(ErrorCode.FileNotFound);
								continue;
							}
							var doc = XmlService.GetXmlFile(fullPath);
							r.OldContent = doc.OuterXml;
							foreach (var i in ef.instructions)
							{
								try
								{
									var xml = doc.OuterXml;
									var itext = i.ToString();
									InstructionService.ApplyXmlInstruction(doc, i);
								}
								catch(Exception x)
								{
									r.SetErrorCode(ErrorCode.InstructionFailed, i.ToString() + " - " + x.Message);
									goto NEXT;
								}
							}
							r.NewContent = doc.OuterXml;
							if (request.writeTarget)
								doc.Save(targetFullPath);
						}
						if (f is AddXmlFile)
						{
							var af = (AddXmlFile)f;
							r.Action = "add";
							r.NewContent = af.xml;
							if (request.writeTarget)
								FileService.WriteFile(targetFullPath, af.xml);
						}
						if(f is EditPropertyFile)
						{
							var epf = (EditPropertyFile)f;
							r.Action="edit";

							if (!System.IO.File.Exists(fullPath))
							{
								r.SetErrorCode(ErrorCode.FileNotFound);
								continue;
							}
							r.OldContent = FileService.GetFileContent(fullPath);
							var pl = PropertyService.Parse(r.OldContent);
							pl = PropertyService.UnEscape(pl);
							foreach (var i in epf.instructions)
							{
								try
								{
									InstructionService.ApplyPropertyInstruction(pl, i);
								}
								catch (Exception x)
								{
									r.SetErrorCode(ErrorCode.InstructionFailed, i.ToString() + " - " + x.Message);
									goto NEXT;
								}
							}
							pl = PropertyService.Escape(pl);
							r.NewContent = PropertyService.Serialize(pl);							
							if (request.writeTarget)
								FileService.WriteFile(targetFullPath, r.NewContent);
						}
						if (f is AddPropertyFile)
						{
							var apf = (AddPropertyFile)f;
							r.Action = "add";
							IList<Property> pl = new List<Property>();
							foreach (var i in apf.instructions)
							{
								try
								{
									InstructionService.ApplyPropertyInstruction(pl, i);
								}
								catch (Exception x)
								{
									r.SetErrorCode(ErrorCode.InstructionFailed, i.ToString() + " - " + x.Message);
									goto NEXT;
								}
							}
							pl = PropertyService.Escape(pl);
							r.NewContent = PropertyService.Serialize(pl);
							if (request.writeTarget)
								FileService.WriteFile(targetFullPath, r.NewContent);
						}
					}
				NEXT:
					continue;
				}
			}
			return result.Values.ToArray();
		}

		public string GetEnvironmentPath(string rootFolder, string environment)
		{
			if(!rootFolder.EndsWith("\\") && !rootFolder.EndsWith("/"))
				rootFolder+= "\\";
			return rootFolder + environment;
		}

		public string GetFilePath(string prefix, string filePath)
		{
			if (!filePath.StartsWith("\\") && !filePath.StartsWith("/"))
				filePath = "\\" + filePath;
			return prefix + filePath;
		}
		
	}
}
